/**
 * Project Name:payment File Name:SignUtils.java Package Name:cn.swiftpass.utils.payment.sign
 * Date:2014-6-27下午3:22:33
 */
package com.acooly.module.openapi.client.provider.jyt.utils;

import com.acooly.core.common.exception.BusinessException;
import com.acooly.core.utils.Strings;
import com.acooly.core.utils.security.RSA;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.*;

/**
 * ClassName:SignUtils Function: 签名用的工具箱 Date: 2014-6-27 下午3:22:33
 *
 * @author
 */
@Slf4j
public class SignUtils {

  public static final String RSA_CHIPER = "RSA/ECB/PKCS1Padding";

  /** 编码 */
  public static final String ENCODE = "UTF-8";

  /** 1024bit 加密块 大小 */
  public static final int ENCRYPT_KEYSIZE = 117;
  /** 1024bit 解密块 大小 */
  public static final int DECRYPT_KEYSIZE = 128;

  /**
   * 过滤参数
   *
   * @param sArray
   * @return
   * @author
   */
  public static Map<String, String> paraFilter(Map<String, String> sArray) {
    Map<String, String> result = new HashMap<String, String>(sArray.size());
    if (sArray == null || sArray.size() <= 0) {
      return result;
    }
    for (String key : sArray.keySet()) {
      String value = sArray.get(key);
      if (value == null || value.equals("") || key.equalsIgnoreCase("sign")) {
        continue;
      }
      result.put(key, value);
    }
    return result;
  }

  /**
   * <一句话功能简述> <功能详细描述>将map转成String
   *
   * @param payParams
   * @return
   * @see [类、类#方法、类#成员]
   */
  public static String payParamsToString(Map<String, String> payParams) {
    return payParamsToString(payParams, false);
  }

  public static String payParamsToString(Map<String, String> payParams, boolean encoding) {
    return payParamsToString(new StringBuilder(), payParams, encoding);
  }

  /**
   * @param payParams
   * @return
   * @author
   */
  public static String payParamsToString(
      StringBuilder sb, Map<String, String> payParams, boolean encoding) {
    buildPayParams(sb, payParams, encoding);
    return sb.toString();
  }

  /**
   * @param payParams
   * @return
   * @author
   */
  public static void buildPayParams(
      StringBuilder sb, Map<String, String> payParams, boolean encoding) {
    List<String> keys = new ArrayList<String>(payParams.keySet());
    Collections.sort(keys);
    for (String key : keys) {
      sb.append(key).append("=");
      if (encoding) {
        sb.append(urlEncode(payParams.get(key)));
      } else {
        sb.append(payParams.get(key));
      }
      sb.append("&");
    }
    sb.setLength(sb.length() - 1);
  }

  /**
   * @param payParams
   * @return
   * @author
   */
  public static String encodeBizContent(String payParams) {
    LinkedHashMap<String, String> jsonMap =
        JSON.parseObject(payParams, new TypeReference<LinkedHashMap<String, String>>() {});
    JSONObject jsonObject = new JSONObject(true);
    for (Map.Entry<String, String> entry : jsonMap.entrySet()) {
      jsonObject.put(entry.getKey(), urlEncode(entry.getValue()));
    }
    return jsonObject.toJSONString();
  }

  public static String urlEncode(String str) {
    try {
      return URLEncoder.encode(str, "UTF-8");
    } catch (Throwable e) {
      return str;
    }
  }

  /**
   * 遍历以及根据重新排序
   *
   * @param sortedParams
   * @return
   */
  public static String getSignContent(Map<String, String> sortedParams) {
    StringBuffer content = new StringBuffer();
    List<String> keys = new ArrayList<String>(sortedParams.keySet());
    Collections.sort(keys);
    int index = 0;
    for (int i = 0; i < keys.size(); i++) {
      String key = keys.get(i);
      String value = sortedParams.get(key);
      if (Strings.isNotBlank(value)) {
        content.append((index == 0 ? "" : "&") + key + "=" + value);
        index++;
      }
    }
    return content.toString();
  }

  /**
   * 宝付RSA私钥验签
   *
   * @param signStr
   * @param keystoreUri
   * @param keystoreType
   * @param keystorePassword
   * @return
   */
  public static String bfSignRSA(
      String signStr, String keystoreUri, String keystoreType, String keystorePassword) {
    PrivateKey privateKey =
        RSA.loadPrivateKeyFromKeyStore(keystoreUri, keystoreType, keystorePassword);
    return encryptByPrivateKey(signStr, privateKey);
  }

  /**
   * 根据私钥加密
   *
   * @param src
   * @param privateKey
   */
  public static String encryptByPrivateKey(String src, PrivateKey privateKey) {

    byte[] destBytes = rsaByPrivateKey(src.getBytes(), privateKey, Cipher.ENCRYPT_MODE);

    if (destBytes == null) {
      throw new BusinessException("私钥签名失败");
    }
    return StringHelper.byte2Hex(destBytes);
  }

  /**
   * 根据公钥解密
   *
   * @param src
   * @param publicKey
   * @return
   */
  public static String decryptByPublicKey(byte[] src, PublicKey publicKey) {

    try {
      byte[] destBytes = rsaByPublicKey(src, publicKey, Cipher.DECRYPT_MODE);

      if (destBytes == null) {
        throw new BusinessException("公钥验签失败");
      }
      return new String(destBytes, ENCODE);
    } catch (UnsupportedEncodingException e) {
      log.error("解密内容不是正确的UTF8格式:", e);
    }
    return null;
  }

  /**
   * RAS加密
   *
   * @param publicKey 公钥
   * @param plainBytes 待加密信息
   * @return byte[]
   * @throws Exception
   */
  public static byte[] encryptRSA(
      byte[] plainBytes, PublicKey publicKey, boolean useBase64Code, String charset) {
    try {
      String CIPHER_ALGORITHM = "RSA/ECB/PKCS1Padding"; // 加密block需要预留11字节
      int KEYBIT = 2048;
      int RESERVEBYTES = 11;
      Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
      int decryptBlock = KEYBIT / 8; // 256 bytes
      int encryptBlock = decryptBlock - RESERVEBYTES; // 245 bytes
      // 计算分段加密的block数 (向上取整)
      int nBlock = (plainBytes.length / encryptBlock);
      if ((plainBytes.length % encryptBlock) != 0) { // 余数非0，block数再加1
        nBlock += 1;
      }
      // 输出buffer, 大小为nBlock个decryptBlock
      ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * decryptBlock);
      cipher.init(Cipher.ENCRYPT_MODE, publicKey);
      // cryptedBase64Str =
      // Base64.encodeBase64String(cipher.doFinal(plaintext.getBytes()));
      // 分段加密
      for (int offset = 0; offset < plainBytes.length; offset += encryptBlock) {
        // block大小: encryptBlock 或 剩余字节数
        int inputLen = (plainBytes.length - offset);
        if (inputLen > encryptBlock) {
          inputLen = encryptBlock;
        }
        // 得到分段加密结果
        byte[] encryptedBlock = cipher.doFinal(plainBytes, offset, inputLen);
        // 追加结果到输出buffer中
        outbuf.write(encryptedBlock);
      }
      // 如果是Base64编码，则返回Base64编码后的数组
      if (useBase64Code) {
        return Base64.getEncoder().encodeToString(outbuf.toByteArray()).getBytes(charset);
      } else {
        return outbuf.toByteArray(); // ciphertext
      }
    } catch (Exception e) {
      throw new BusinessException("公钥加密异常：" + e.getMessage());
    }
  }

  /**
   * RSA解密
   *
   * @param privateKey 私钥
   * @param cryptedBytes 待解密信息
   * @return byte[]
   * @throws Exception
   */
  public static byte[] decryptRSA(byte[] cryptedBytes, PrivateKey privateKey, boolean useBase64Code, String charset) {
    try {
      String CIPHER_ALGORITHM = "RSA/ECB/PKCS1Padding"; // 加密block需要预留11字节
      byte[] data = null;

      // 如果是Base64编码的话，则要Base64解码
      if (useBase64Code) {
        data = Base64.getDecoder().decode(new String(cryptedBytes, charset));
      } else {
        data = cryptedBytes;
      }

      int KEYBIT = 2048;
      int RESERVEBYTES = 11;
      Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
      int decryptBlock = KEYBIT / 8; // 256 bytes
      int encryptBlock = decryptBlock - RESERVEBYTES; // 245 bytes
      // 计算分段解密的block数 (理论上应该能整除)
      int nBlock = (data.length / decryptBlock);
      // 输出buffer, , 大小为nBlock个encryptBlock
      ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * encryptBlock);
      cipher.init(Cipher.DECRYPT_MODE, privateKey);
      // plaintext = new
      // String(cipher.doFinal(Base64.decodeBase64(cryptedBase64Str)));
      // 分段解密
      for (int offset = 0; offset < data.length; offset += decryptBlock) {
        // block大小: decryptBlock 或 剩余字节数
        int inputLen = (data.length - offset);
        if (inputLen > decryptBlock) {
          inputLen = decryptBlock;
        }

        // 得到分段解密结果
        byte[] decryptedBlock = cipher.doFinal(data, offset, inputLen);
        // 追加结果到输出buffer中
        outbuf.write(decryptedBlock);
      }
      outbuf.flush();
      outbuf.close();
      return outbuf.toByteArray();
    } catch (Exception e) {
      throw new BusinessException("私钥解密异常：" + e.getMessage());
    }
  }

  /**
   * 私钥算法
   *
   * @param srcData 源字节
   * @param privateKey 私钥
   * @param mode 加密 OR 解密
   * @return
   */
  public static byte[] rsaByPrivateKey(byte[] srcData, PrivateKey privateKey, int mode) {
    try {
      Cipher cipher = Cipher.getInstance(RSA_CHIPER);
      cipher.init(mode, privateKey);
      // 分段加密
      int blockSize = (mode == Cipher.ENCRYPT_MODE) ? ENCRYPT_KEYSIZE : DECRYPT_KEYSIZE;
      byte[] decryptData = null;
      for (int i = 0; i < srcData.length; i += blockSize) {
        byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(srcData, i, i + blockSize));
        decryptData = ArrayUtils.addAll(decryptData, doFinal);
      }
      return decryptData;
    } catch (NoSuchAlgorithmException e) {
      log.error("私钥算法-不存在的解密算法:", e);
    } catch (NoSuchPaddingException e) {
      log.error("私钥算法-无效的补位算法:", e);
    } catch (IllegalBlockSizeException e) {
      log.error("私钥算法-无效的块大小:", e);
    } catch (BadPaddingException e) {
      log.error("私钥算法-补位算法异常:", e);
    } catch (InvalidKeyException e) {
      log.error("私钥算法-无效的私钥:", e);
    }
    return null;
  }

  /**
   * 公钥算法
   *
   * @param srcData 源字节
   * @param publicKey 公钥
   * @param mode 加密 OR 解密
   * @return
   */
  public static byte[] rsaByPublicKey(byte[] srcData, PublicKey publicKey, int mode) {
    try {
      Cipher cipher = Cipher.getInstance(RSA_CHIPER);
      cipher.init(mode, publicKey);
      // 分段加密
      int blockSize = (mode == Cipher.ENCRYPT_MODE) ? ENCRYPT_KEYSIZE : DECRYPT_KEYSIZE;
      byte[] encryptedData = null;
      for (int i = 0; i < srcData.length; i += blockSize) {
        // 注意要使用2的倍数，否则会出现加密后的内容再解密时为乱码
        byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(srcData, i, i + blockSize));
        encryptedData = ArrayUtils.addAll(encryptedData, doFinal);
      }
      return encryptedData;
    } catch (NoSuchAlgorithmException e) {
      log.error("公钥算法-不存在的解密算法:", e);
    } catch (NoSuchPaddingException e) {
      log.error("公钥算法-无效的补位算法:", e);
    } catch (IllegalBlockSizeException e) {
      log.error("公钥算法-无效的块大小:", e);
    } catch (BadPaddingException e) {
      log.error("公钥算法-补位算法异常:", e);
    } catch (InvalidKeyException e) {
      log.error("公钥算法-无效的私钥:", e);
    }
    return null;
  }
}
